//
//  B1SyncData.swift
//  Pods
//
//  Created by tom on 2025/9/29.
//
import Foundation
import Combine

//B1设备支持断点续传的固件
fileprivate let b1VersionSupportBreakpoint = 110039250728

class B1SyncDataProcess{
    let address: String
    let deleteBeforeWrite: Bool?
    let peripheralManager:PeripheralManager
    var targetFilePath: String? = nil
    let filePath:String?
    let blePacketSubject:PassthroughSubject<BlePacket, Never>
    init(address:String, filePath:String?, deleteBeforeWrite:Bool?, peripheralManager:PeripheralManager) {
        self.address = address
        self.deleteBeforeWrite = deleteBeforeWrite
        self.peripheralManager = peripheralManager
        self.filePath = filePath
        self.blePacketSubject = peripheralManager.blePacketSubjects[address]!
    }
    
    private let frameSize = 184
    private var fileLength = 0
    private var continuation:AsyncThrowingStream<B1SyncDataEvent, Error>.Continuation? = nil
    
    
    func processing() -> AsyncThrowingStream<B1SyncDataEvent, Error>{
        return AsyncThrowingStream<B1SyncDataEvent, Error> {[weak self] continuation in
            self?.continuation = continuation
            let task = Task{
                do{
                    try await withTaskCancellationHandler {
                        try await self?.process()
                        continuation.finish()
                    } onCancel: {[weak self] in
                        if let address = self?.address{
                            continuation.yield(B1SyncDataEvent(
                                address: address,
                                state:.canceled,
                            ))
                        }
                        
                    }
                    
                }catch{
                    continuation.finish(throwing: error)
                    
                }
            }
            continuation.onTermination = {[weak self] _ in
                self?.continuation = nil
                task.cancel()
            }
            
            
        }
    }
    
    
    private func process()async throws{
        
        continuation?.yield(B1SyncDataEvent(
            address: address,
            state:.start,
        ))
        let fileHandle = try await initFileHandle()
        
        defer{
            try? fileHandle.close()
        }
        
        let firmwareVerision = try await checkFirmwareVerision()
        
        if firmwareVerision >= b1VersionSupportBreakpoint{
            /// 支持断点续传
            try await syncWithResumeable(fileHandle: fileHandle)
        }else{
            /// 不支持支持断点续传
            try await sync(fileHandle:fileHandle)
        }
        
    }
    
    private func syncWithResumeable(fileHandle:FileHandle) async throws{
        let lastIndex:Int = fileLength / frameSize
        var index = lastIndex
        var lastErrorIndex = -1
        var errorCount = 0
        
        let flashCountResp = try await peripheralManager.request(address: address, cmd: .measFlashCount, data: nil)
        guard let data = flashCountResp.data else { throw BleError.syncDataCmdRespError }
        let flashCount = try data.toLitteEndianInt32()
        
        //        let p:PassthroughSubject<BlePacket, Never> = PassthroughSubject()
        while index + 1 < flashCount {
            let stream :AsyncThrowingStream<BlePacket, any Error> = AsyncThrowingStream { continuation in
                let cancellable = blePacketSubject
                    .filter({ packet in
                        packet.cmd == .measFlash
                    })
                    .timeout(.seconds(2), scheduler: DispatchQueue.main)
                    .sink { completion in
                        switch completion{
                        case .finished:
                            debugPrint("B1SyncDataProcess  stream finished:\(completion)")
                            continuation.finish()
                        case .failure(let error):
                            debugPrint("B1SyncDataProcess  stream failure:\(error)")
                            continuation.finish(throwing: error)
                        }
                    } receiveValue: { packet in
                        continuation.yield(packet)
                    }
                continuation.onTermination = { _ in
                    cancellable.cancel()
                }
            }
            try Task.checkCancellation()
            try startSyncData(index)
            try Task.checkCancellation()
            debugPrint("B1SyncDataProcess  lastErrorIndex:\(lastErrorIndex),errorCount:\(errorCount),index:\(index)")
            if lastErrorIndex == Int(index){
                errorCount += 1
            }else{
                lastErrorIndex = Int(index)
                errorCount = 0
            }
            
            if errorCount >= 10{
                throw BleError.b1SyncDataTooManyTimes
            }
            for try await packet in stream{
                guard let data = packet.data else {
                    throw BleError.requestTimeout
                }
                var binary = Binary(bytes: [0, data[2], data[1], data[0]])
                guard let receivedIndex = try? binary.readInt32()else{
                    throw BleError.requestTimeout
                }
                try Task.checkCancellation()
                
                if receivedIndex == index {
                    try fileHandle.seek(toOffset: UInt64(frameSize * index))
                    if #available(iOS 13.4, *) {
                        try fileHandle.write(contentsOf: packet.raw)
                    } else {
                        fileHandle.write(packet.raw)
                    }
                    continuation?.yield(B1SyncDataEvent(
                        address: address,
                        state:.progress,
                        receivedSize:Int64(index),
                        totalSize:Int64(flashCount)
                    ))
                    
                    index += 1
                } else {
                    
                    try? peripheralManager.write(address: address, cmd: .stopSync, data: nil)
                    break
                }
            }
            
        }
        
        continuation?.yield(B1SyncDataEvent(
            address: address,
            state:.progress,
            receivedSize:Int64(flashCount),
            totalSize:Int64(flashCount)
        ))
    }
    
    /// 老版本，通过补帧的方式同步数据
    private func sync(fileHandle:FileHandle) async throws{
        func writeData(_ packet:BlePacket)throws ->Int{
            guard let data = packet.data else {
                throw BleError.requestTimeout
            }
            var binary = Binary(bytes: [0, data[2], data[1], data[0]])
            guard let receivedIndex32 = try? binary.readInt32() else{
                throw BleError.requestTimeout
            }
            let receivedIndex = Int(receivedIndex32)
            try Task.checkCancellation()
            try fileHandle.seek(toOffset: UInt64(frameSize * receivedIndex))
            if #available(iOS 13.4, *) {
                try fileHandle.write(contentsOf: packet.raw)
            } else {
                fileHandle.write(packet.raw)
            }
            return receivedIndex
        }
        let flashCountResp = try await peripheralManager.request(address: address, cmd: .measFlashCount, data: nil)
        guard let data = flashCountResp.data else { throw BleError.syncDataCmdRespError }
        let flashCount = try data.toLitteEndianInt32()
        
        let stream :AsyncThrowingStream<BlePacket, any Error> = AsyncThrowingStream { continuation in
            let cancellable = blePacketSubject
                .filter({ packet in
                    packet.cmd == .measFlash
                })
                .timeout(.seconds(2), scheduler: DispatchQueue.main)
                .sink { completion in
                    switch completion{
                    case .finished:
                        debugPrint("B1SyncDataProcess  stream finished:\(completion)")
                        continuation.finish()
                    case .failure(let error):
                        debugPrint("B1SyncDataProcess  stream failure:\(error)")
                        continuation.finish(throwing: error)
                    }
                } receiveValue: { packet in
                    continuation.yield(packet)
                }
            continuation.onTermination = { _ in
                cancellable.cancel()
            }
        }
        var index = -1
        try startSyncData(0)
        var lostIndex = [Int]()
        var receivedCount = 0
        for try await packet in stream{
            receivedCount += 1
           
            let receivedIndex = try writeData(packet)
            continuation?.yield(B1SyncDataEvent(
                address: address,
                state:.progress,
                receivedSize:Int64(receivedCount),
                totalSize:Int64(flashCount)
            ))
            
            if index == -1{
                index = receivedIndex
            }
            if index < receivedIndex{
                for i in index..<receivedIndex{
                    lostIndex.append(i)
                }
            }
            
            index = receivedIndex + 1
        }
        debugPrint("B1SyncDataProcess 丢帧:\(lostIndex.count)")
        for index in lostIndex{
            var binary = Binary()
            binary.writeInt(Int32(index), littleEndian: true)
            let array = try binary.readBytes(3)
            let resp = try await peripheralManager.request(address: address, cmd: .measIndex, data: Data(array))
            let receivedIndex = try writeData(resp)
            if receivedIndex == index{
                receivedCount += 1
                continuation?.yield(B1SyncDataEvent(
                    address: address,
                    state:.progress,
                    receivedSize:Int64(receivedCount),
                    totalSize:Int64(flashCount)
                ))
                debugPrint("B1SyncDataProcess 补帧:index:\(index)")
            }else{
                debugPrint("B1SyncDataProcess 补帧失败:\(receivedIndex),index:\(index)")
            }
            
            
        }
        
        continuation?.yield(B1SyncDataEvent(
            address: address,
            state:.progress,
            receivedSize:Int64(flashCount),
            totalSize:Int64(flashCount)
        ))
    }
    
    private func startSyncData(_ index:Int)throws{
        var binary = Binary()
        binary.writeInt(Int32(index), littleEndian: true)
        debugPrint("B1SyncDataProcess  startSyncData:\(index)")
        try Task.checkCancellation()
        try peripheralManager.write(address: address, cmd: .measFlash, data: Data(binary.getAllBytes().prefix(3)))
        try Task.checkCancellation()
    }
    
    private func initFileHandle()async throws -> FileHandle{
        var targetFilePath:String = filePath ?? ""
        if filePath == nil{
            try Task.checkCancellation()
            let resp = try await peripheralManager.request(address: address, cmd: .timeCache, data: Data([0]))
            try Task.checkCancellation()
            var time = 0
            if resp.data != Data([0xff, 0xff, 0xff, 0xff]){
                var b = Binary(bytes: [UInt8](resp.data!).reversed())
                let t = try b.readSignedBytes(4)
                time = t * 1000
                
            }
            targetFilePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0] + "/\(time).raw"
        }
        if deleteBeforeWrite == true{
            if FileManager.default.fileExists(atPath: targetFilePath){
                try FileManager.default.removeItem(atPath: targetFilePath)
            }
        }
        
        if !FileManager.default.fileExists(atPath: targetFilePath){
            FileManager.default.createFile(atPath: targetFilePath, contents: nil)
        }
        guard let fileHandle = FileHandle(forWritingAtPath: targetFilePath) else {
            throw BleError.initFileHandleFail
        }
        if let fileAttributes = try? FileManager.default.attributesOfItem(atPath: targetFilePath){
            if  let fileLength = fileAttributes[.size] as? Int{
                self.fileLength = fileLength
            }
        }
        
        
        self.targetFilePath = targetFilePath
        
        return fileHandle
    }
    
    private func checkFirmwareVerision() async throws -> Int{
        var firmwareVersion = 0
        let infoResp = try await peripheralManager.request(address: address, cmd: .info, data: nil)
        if let data = infoResp.data{
            var binary = Binary(bytes: [UInt8](data))
            let battery = try binary.readInt8()
            let hardwareLength = try binary.readInt8()
            let hardwareData = try binary.readBytes(Int(hardwareLength))
            let hardware = String(data: Data(hardwareData), encoding: .utf8)
            
            let firmwareLength = try binary.readInt8()
            let firmwareData = try binary.readBytes(Int(firmwareLength))
            let firmware = String(data: Data(firmwareData), encoding: .utf8)
            if let str = firmware?.extractDigit(), let n = Int(str){
                firmwareVersion = n
            }
        }
        return firmwareVersion
    }
}
